----------------------------------
-- Corona Dialog Library		--
-- Made By: Tobiah Marks		--
-- Last Updated: Jan 25th, 2011	--
-- Version Number: 0.3.5		--
----------------------------------
-- Usage:
-- 		1. Add this file to the folder of your Corona program.
--		2. Add this to the top of the file you wish to call a dialog in: require "dialog"
--		3. Call a new dialog, defining variables as desired.
--		To Close:
--			Use close(dialog) or closeID("dialogID")
--		To Change Default Values:
--			Have a certain way you want to make all your dialogs look? Save some time and change the default values!
--			Use setDefault("variable",value) to change dialog variables, and setButtonDefault("variable",value) to change button variables.
--			NOTE:
--				You cannot add a default displayObject, as you pass a reference to a object.
--				However you can add default images for backgrounds/buttons, as you pass image names.
-- 		Example:
--			require "dialog"
--
--			...
--
--			dialog.setDefault ("textColor", { 255, 0, 0, 255 }) --Change the default for text color to red.
--			dialog.setButtonDefault ("text", "Accept") --Change the default button text to "Accept".
--
--			...
--
--			local dialog1 = dialog.new	
--				text = "Answer this question!",
--				buttons =  {
--					{text="Yes!",color={50,255,50},onRelease=someFunction},
--					{text="No!",color={255,50,50}} --No function declared, so default closes dialog.
--				},
--				id = "question1" --Required for having more than one dialouge box!
--			}
--
--			...
--
--			close(dialog1) --Closes dialouge based on reference.
--			--closeID("question1") -Or close dialog based on ID.
----------------------------------
-- Variable Definations:
-- 		text, Text to display! Default: Won't add text
--		displayObject, a display object to add to the top of the dialog. Default: Cannot have default
-- 		fontSize, Size of the text. Default: 18
-- 		font, Which font to use. Default: System bold
-- 		textColor, Text color. {R,G,B[,A]} Default: White
-- 		topMargin, The margin from the top of the dialog box to the top of the screen. Pixal value. Default: widthMargin*2
--		contentTopMargin, the margin from the top of the content to the top of the dialog. Default: 0
--		contentWidthMargin, the margin on the sides of the content within the dialog. Defaut: 0
-- 		id, The ID of the dialog. Used for closing the dialog based on ID. Default: "No ID"
--		background, the background image of the dialouge box. Default: nil (uses RoundedRect)
-- 		widthPercentage, The width of the dialog box to the edge of the screen, by percentage screen width. Used only if not using images. Default: 0.05
-- 		backgroundColor, Color of the background. Used only if not using images. {R,G,B[,A]} Default: {140, 140, 140}
-- 		backgroundStrokeColor, Color of the background's stroke. Used only if not using images. {R,G,B[,A]} Default: {180, 180, 180}
--		backgroundStrokeSize, The size of the background's stroke. Used only if not using images. Default: 3
-- 		buttons, The table of information (outlined below) for the dialog buttons. Default: {}
--		
--		Button Variables:
--			buttons[#].text, The labels for the button(s) of the dialog box. Default: "OK"
--			buttons[#].onRelease, the functons that triggers on release of the buttons. Default: onRelease=nil, onEvent=buttonHandler (closes dialog)
--			buttons[#].default, the default button image. Default: nil (uses RoundedRect)
--			buttons[#].over, the on press button image. Default: nil (uses RoundedRect)
--			buttons[#].width, The with of the dialog buttons. Used only if not using images. Default: display.contentWidth/3.5
--			buttons[#].color, The color of the button. Used only if not using images. {R,G,B[,A]} Default: {140, 140, 140}
--			buttons[#].strokeColor, The stroke color of the button. Used only if not using images. {R,G,B[,A]} Default: {180, 180, 180}
--			buttons[#].overColor, the color of the button on press. Used only if not using images. {R,G,B[,A]} Default: {0, 140, 140}
--			buttons[#].overStrokeColor, the stroke color of the button on press. Used only if not using images. {R,G,B[,A]} Default: {0, 180, 180}
--			buttons[#].strokeSize, the size of button's stroke. Used only if not using images. Default: 3
----------------------------------
-- TO DO:
--		Change alignment of display object?
----------------------------------
-- Version History:
-- 		0.1:	Release.
--		0.2:	Metatables, assert ID exists on closeID, fixed bug with onRelease.
--		0.3:	Metatables working with buttons.
--		0.3.1:	Changable background for button.
--		0.3.2:	Changed name to "Dialog" from "Dialogue". Because hey, it saves two letters of typing.
--				Fixed a bug where you couldn't have 0 buttons on a dialog.
--				Randomly assigns a ID if none is provided, rather than having "No id"
--		0.3.3:	Able to pass a display object to display top and centered in app.
--				Dialog now scales to largest button size, instead of just first.
--				Text is now optional
--		0.3.4:	Added setDefault and setButtonDefault.
--				Added buttons[#].strokeSize
--		0.3.5:	Added contentWidthMargin and contentTopMargin
----------------------------------

module(..., package.seeall)

--local textSize
local dialogs

---------------
-- Stolen from Cromax on the Code Exchange. http://developer.anscamobile.com/code/multiline-text-width-pixel
-- Modified for this library, use cation if copy/pasting elsewhere.
---------------
local function dialogWrappedText(text, font, size, color, width)
		-- Commented out, because in context of Dialog library redundent code.
        --if text == '' then return false end
        --font = font or native.systemFont
        --size = tonumber(size) or 12
        --color = color or {255, 255, 255}
        --width = width or display.stageWidth
 
        local result = display.newGroup()
        local currentLine = ''
        local currentLineLength = 0
        local lineCount = 0
        local left = 0
        for line in string.gmatch(text, "[^\n]+") do
                for word, spacer in string.gmatch(line, "([^%s%-]+)([%s%-]*)") do
                        local tempLine = currentLine..word..spacer
                        local tempDisplayLine = display.newText(tempLine, 0, 0, font, size)
                        if tempDisplayLine.width <= width then
                                currentLine = tempLine
                                currentLineLength = tempDisplayLine.width
                        else
                                local newDisplayLine = display.newText(currentLine, 0, (size * 1.3) * (lineCount - 1), font, size)
                                newDisplayLine:setTextColor(color[1], color[2], color[3])
                                result:insert(newDisplayLine)
                                lineCount = lineCount + 1
                                if string.len(word) <= width then
                                        currentLine = word..spacer
                                        currentLineLength = string.len(word)
                                else
                                        local newDisplayLine = display.newText(word, 0, (size * 1.3) * (lineCount - 1), font, size)
                                        newDisplayLine:setTextColor(color[1], color[2], color[3])
                                        result:insert(newDisplayLine)
                                        lineCount = lineCount + 1
                                        currentLine = ''
                                        currentLineLength = 0
                                end 
                        end
                        tempDisplayLine:removeSelf();
						tempDisplayLine=nil;
                end
                local newDisplayLine = display.newText(currentLine, 0, (size * 1.3) * (lineCount - 1), font, size)
                newDisplayLine:setTextColor(color[1], color[2], color[3])
                result:insert(newDisplayLine)
                lineCount = lineCount + 1
                currentLine = ''
                currentLineLength = 0
        end
        result:setReferencePoint(display.TopCenterReferencePoint)
        
        --textSize = (size * 1.3) * (lineCount) -- Save the size so we can make a appropiate sized bounding box.
        return result, (size * 1.3) * (lineCount) 
end

-----------------
-- Stolen from ui.lua Version 1.5, unchanged.
-- Helper function for newButton utility function below
-----------------
local function newButtonHandler( self, event )

	local result = true

	local default = self[1]
	local over = self[2]
	
	-- General "onEvent" function overrides onPress and onRelease, if present
	local onEvent = self._onEvent
	
	local onPress = self._onPress
	local onRelease = self._onRelease

	local buttonEvent = {}
	if (self._id) then
		buttonEvent.id = self._id
	end

	local phase = event.phase
	if "began" == phase then
		if over then 
			default.isVisible = false
			over.isVisible = true
		end

		if onEvent then
			buttonEvent.phase = "press"
			result = onEvent( buttonEvent )
		elseif onPress then
			result = onPress( event )
		end

		-- Subsequent touch events will target button even if they are outside the contentBounds of button
		display.getCurrentStage():setFocus( self, event.id )
		self.isFocus = true
		
	elseif self.isFocus then
		local bounds = self.contentBounds
		local x,y = event.x,event.y
		local isWithinBounds = 
			bounds.xMin <= x and bounds.xMax >= x and bounds.yMin <= y and bounds.yMax >= y

		if "moved" == phase then
			if over then
				-- The rollover image should only be visible while the finger is within button's contentBounds
				default.isVisible = not isWithinBounds
				over.isVisible = isWithinBounds
			end
			
		elseif "ended" == phase or "cancelled" == phase then 
			if over then 
				default.isVisible = true
				over.isVisible = false
			end
			
			if "ended" == phase then
				-- Only consider this a "click" if the user lifts their finger inside button's contentBounds
				if isWithinBounds then
					if onEvent and not onRelease then
						buttonEvent.phase = "release"
						result = onEvent( buttonEvent )
					elseif onRelease then
						result = onRelease( event )
					end
				end
			end
			
			-- Allow touch events to be sent normally to the objects they "hit"
			display.getCurrentStage():setFocus( self, nil )
			self.isFocus = false
		end
	end

	return result
end

---------------
-- Button class, Stolen from ui.lua Version 1.5, tweaked to use rounded rect as well as images.
---------------
local function newDialogButton( params )
	local button, default, over
	local offset --Unused, but saving.
	
	button = display.newGroup()
	
	if params.default then
		default = display.newImage( params.default )
	else
		default = display.newRoundedRect ( 0,0,params.width,(params.size * 1.3), 12 )
		default.strokeWidth = params.strokeSize
		default:setFillColor(params.color[1],params.color[2],params.color[3],params.color[4] or 255)
		default:setStrokeColor(params.strokeColor[1],params.strokeColor[2],params.strokeColor[3],params.strokeColor[4] or 255)
	end
	button:insert( default, true )
	
	if params.over then
		over = display.newImage( params.over )
	else
		over = display.newRoundedRect ( 0,0,params.width,(params.size * 1.3), 12 )
		over.strokeWidth = params.strokeSize
		over:setFillColor(params.overColor[1],params.overColor[2],
						  params.overColor[3],params.overColor[4] or 255)
		over:setStrokeColor(params.overStrokeColor[1],params.overStrokeColor[2],
							params.overStrokeColor[3],params.overStrokeColor[4] or 255)
	end
	over.isVisible = false
	button:insert( over, true )
	
	-- Public methods
	function button:setText( newText )
	
		local labelText = self.text
		if ( labelText ) then
			labelText:removeSelf()
			self.text = nil
		end

		local labelShadow = self.shadow
		if ( labelShadow ) then
			labelShadow:removeSelf()
			self.shadow = nil
		end

		local labelHighlight = self.highlight
		if ( labelHighlight ) then
			labelHighlight:removeSelf()
			self.highlight = nil
		end
		
		-- Optional vertical correction for fonts with unusual baselines (I'm looking at you, Zapfino)
		if ( params.offset and type(params.offset) == "number" ) then offset=params.offset else offset = 0 end
		
		if ( params.emboss ) then
			-- Make the label text look "embossed" (also adjusts effect for textColor brightness)
			local textBrightness = ( params.textColor[1] + params.textColor[2] + params.textColor[3] ) / 3
			
			labelHighlight = display.newText( newText, 0, 0, params.font, params.size )
			if ( textBrightness > 127) then
				labelHighlight:setTextColor( 255, 255, 255, 20 )
			else
				labelHighlight:setTextColor( 255, 255, 255, 140 )
			end
			button:insert( labelHighlight, true )
			labelHighlight.x = labelHighlight.x + 1.5; labelHighlight.y = labelHighlight.y + 1.5 + offset
			self.highlight = labelHighlight

			labelShadow = display.newText( newText, 0, 0, params.font, params.size )
			if ( textBrightness > 127) then
				labelShadow:setTextColor( 0, 0, 0, 128 )
			else
				labelShadow:setTextColor( 0, 0, 0, 20 )
			end
			button:insert( labelShadow, true )
			labelShadow.x = labelShadow.x - 1; labelShadow.y = labelShadow.y - 1 + offset
			self.shadow = labelShadow
		end
		
		labelText = display.newText( newText, 0, 0, params.font, params.size )
		labelText:setTextColor( params.textColor[1], params.textColor[2], params.textColor[3], params.textColor[4] )
		button:insert( labelText, true )
		labelText.y = labelText.y + offset
		self.text = labelText
	end
	
	if params.text then
		button:setText( params.text )
	end
	
	if ( params.onPress and ( type(params.onPress) == "function" ) ) then
		button._onPress = params.onPress
	end
	if ( params.onRelease and ( type(params.onRelease) == "function" ) ) then
		button._onRelease = params.onRelease
	end
	
	if (params.onEvent and ( type(params.onEvent) == "function" ) ) then
		button._onEvent = params.onEvent
	end
		
	-- Set button as a table listener by setting a table method and adding the button as its own table listener for "touch" events
	button.touch = newButtonHandler
	button:addEventListener( "touch", button )

	if params.x then
		button.x = params.x
	end
	
	if params.y then
		button.y = params.y
	end
	
	if params.id then
		button._id = params.id
	end

	return button
end

---------------
-- Close dialog event handler
---------------
local buttonHandler = function( event )
	if (event.phase == "release") then
		if event.id then
			event.id:removeSelf();
			event.id=nil;
		end
	end
end

---------------
-- Close dialog funtions. These are public.
---------------
function closeID( dialogID )
		if (dialogs[dialogID]) then
			dialogs[dialogID]:removeSelf();
			dialogs[dialogID]=nil;
		end
end

function close( dialog )
		dialog:removeSelf();
		dialog=nil;
end

---------------
-- Metatables
---------------
local DefaultParams = {
--	text = nil,
--	displayObject = nil,
	fontSize	= 18,
	font		= native.systemFontBold,
	textColor	= { 255, 255, 255, 255 },
	contentTopMargin = 0,
	contentWidthMargin = 0,
-- 	topMargin	= nil,
	widthPercentage = 0.05,
	buttons		= {},
--	background = nil,
	bgColor		= { 140, 140, 140 },
	bgStrokeColor	= { 180, 180, 180 },
	bgStrokeSize	= 3,
}
DefaultParams.__index = DefaultParams

local DefaultButton = {
	text		  = "OK",
--	default	  = nil,
--	over	  = nil,
	color	  = { 140, 140, 140 },
	strokeColor = { 180, 180, 180 },
	overColor	  = { 0, 140, 140 },
	overStrokeColor = { 0, 180, 180 },
	strokeSize = 3,
	width		  = display.contentWidth/3.5,
--	onRelease	  = nil,
	onEvent		  = buttonHandler,
--	emboss 	  = true,
}
DefaultButton.__index = DefaultButton

---------------
-- Change metatables, Public function
---------------
function setDefault (t, d)
    DefaultParams[t] = d
end
function setButtonDefault (t, d)
    DefaultButton[t] = d
end

---------------
-- Dialog creator, Public function
---------------
function new( params )
	setmetatable( params, DefaultParams )
	
	local dialog = display.newGroup()
	
	local background
	if (params.background) then
		background = display.newImage( params.background )
		background:setReferencePoint( display.TopCenterReferencePoint )
		background.x = display.contentWidth / 2
		background.y = params.topMargin
		params.widthMargin = ((display.contentWidth - background.contentWidth)/2)
	else
		params.widthMargin = (display.contentWidth * params.widthPercentage)
	end
	params.topMargin = params.topMargin or params.widthMargin*2
	
	local contentSize = 0 + params.contentTopMargin
	
	if (params.displayObject) then
		local displayObject = params.displayObject
		displayObject:setReferencePoint( display.TopCenterReferencePoint )
		displayObject.x = display.contentWidth/2 --params.widthMargin + (params.fontSize/2)
		displayObject.y = params.topMargin+contentSize + (params.fontSize/6)
		dialog:insert(displayObject)
		contentSize = contentSize+displayObject.height + (params.fontSize/6)
	end
	
	if (params.text) then
		local t, textSize = dialogWrappedText(  params.text,
					params.font, 
					params.fontSize,
					params.textColor,
					display.contentWidth-(params.widthMargin*2)-(params.fontSize)-(2*params.contentWidthMargin))
		t.x = display.contentWidth/2
		t.y = params.topMargin+contentSize + (params.fontSize/6)
		contentSize = contentSize+textSize + (params.fontSize/6)
	
		dialog:insert(t)
	end

	if (params.buttons) then
		local buttonSize = 0
		for buttonNum,buttonParam in ipairs(params.buttons) do
			setmetatable(buttonParam, DefaultButton)
			if buttonParam.onRelease then buttonParam.onEvent = nil end
			buttonParam.id		= dialog
			buttonParam.textColor	= params.textColor
			buttonParam.size	= params.fontSize
			buttonParam.font	= params.font	

			local button = newDialogButton( buttonParam )

	    	button:setReferencePoint( display.TopCenterReferencePoint )
			button.x = buttonNum * display.contentWidth / ((#params.buttons)+1)
			button.y = params.topMargin+contentSize + (params.fontSize/6)
		
			dialog:insert(button)
			
			if (button.contentHeight > buttonSize) then buttonSize = button.contentHeight end
  		end
		contentSize = contentSize+buttonSize + (params.fontSize/6)
	end

	if (params.background == nil) then
		background = display.newRoundedRect (dialog, params.widthMargin, 
									params.topMargin, 
									display.contentWidth-(params.widthMargin*2), 
									contentSize+(params.fontSize/6),
									 12 )
		background.strokeWidth = params.bgStrokeSize
		background:setFillColor( params.bgColor[1], params.bgColor[2], params.bgColor[3], params.bgColor[4] or 255 )
		background:setStrokeColor(params.bgStrokeColor[1],params.bgStrokeColor[2],params.bgStrokeColor[3],params.bgStrokeColor[4] or 255)
	end
	dialog:insert( background )
	background:toBack()
		
	dialogs = dialogs or {}
	if (params.id) then
		dialogs[id] = dialog
	else
		table.insert(dialogs,dialog)
	end
	
	return dialog
end
